﻿using System.Collections.Generic;
using System.Linq.Expressions;
using Inovout.Descriptor;
using Inovout.Lambda;

namespace System.Linq
{
    public static class QueryableOrderExtensions
    {
        public static IQueryable<TModel> Order<TModel>(this IQueryable<TModel> query, IEnumerable<OrderCriteria> orders) where TModel : class
        {
            return OrderExecutor<TModel>.Execute(query, orders);
        }
        public static IOrderedEnumerable<TModel> Order<TModel>(this IEnumerable<TModel> query, IEnumerable<OrderCriteria> orders) where TModel : class
        {
            return OrderExecutor<TModel>.Execute(query, orders);
        }

        private static class OrderExecutor<TModel> where TModel : class
        {
            internal static IQueryable<TModel> Execute(IQueryable<TModel> query, IEnumerable<OrderCriteria> orderCriterias)
            {
                foreach (var orderCriteria in orderCriterias)
                {
                    query = CachedExpressionCompiler.Process<IQueryable<TModel>, IQueryable<TModel>>(BuildOrderExpression(orderCriteria))(query);
                }
                return query;
            }

            internal static IOrderedEnumerable<TModel> Execute(IEnumerable<TModel> query, IEnumerable<OrderCriteria> orderCriterias)
            {
                IOrderedEnumerable<TModel> order = null;
                foreach (var orderCriteria in orderCriterias)
                {
                    order = CachedExpressionCompiler.Process<IEnumerable<TModel>, IOrderedEnumerable<TModel>>(BuildEnumerableOrderExpression(orderCriteria))(query);
                }
                return order;
            }
            private static Expression<Func<IQueryable<TModel>, IQueryable<TModel>>> BuildOrderExpression(OrderCriteria orderCriteria)
            {
                Type entityType = typeof(TModel);
                Type queryType = typeof(IQueryable<TModel>);

                ParameterExpression typeExpression = Expression.Parameter(entityType, "entity");
                ParameterExpression queryExpression = Expression.Parameter(queryType, "euery");

                LambdaExpression propertyAccessorExpression = PropertyDescriptor.BuildPropertyAccessorExpression(typeExpression, orderCriteria.OrderName);
                Type[] typeArgs = new Type[] { entityType, propertyAccessorExpression.Body.Type };

                MethodCallExpression orderExpression =
                    Expression.Call(typeof(Queryable), orderCriteria.OrderAscending ? "OrderBy" : "OrderByDescending", typeArgs, queryExpression, propertyAccessorExpression);


                return Expression.Lambda<Func<IQueryable<TModel>, IQueryable<TModel>>>(orderExpression, queryExpression);
            }
            private static Expression<Func<IEnumerable<TModel>, IOrderedEnumerable<TModel>>> BuildEnumerableOrderExpression(OrderCriteria orderCriteria)
            {
                Type entityType = typeof(TModel);
                Type queryType = typeof(IEnumerable<TModel>);

                ParameterExpression typeExpression = Expression.Parameter(entityType, "entity");
                ParameterExpression queryExpression = Expression.Parameter(queryType, "euery");

                LambdaExpression propertyAccessorExpression = PropertyDescriptor.BuildPropertyAccessorExpression(typeExpression, orderCriteria.OrderName);
                Type[] typeArgs = new Type[] { entityType, propertyAccessorExpression.Body.Type };

                MethodCallExpression orderExpression =
                    Expression.Call(typeof(Enumerable), orderCriteria.OrderAscending ? "OrderBy" : "OrderByDescending", typeArgs, queryExpression, propertyAccessorExpression);


                return Expression.Lambda<Func<IEnumerable<TModel>, IOrderedEnumerable<TModel>>>(orderExpression, queryExpression);
            }

        }
    }
}
